package parsing;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import exceptions.ArgumentsException;
import expressions.Expression;

/**
 * The Parser class turns Strings into Expressions of a certain type via the
 * Shunting-Yard algorithm.
 */
public class Parser<T>
{
    private ExpressionLibrary library;

    private List<Expression<T>> input;
    private int index;
    private Stack<Expression<T>> opStack, outStack;

    private boolean operatorExpected;

    /**
     * Constructs a Parser that works with the expressions defined in a package.
     * 
     * TODO: the context parameter isn't necessary, should be eliminated.
     * 
     * @param packageName
     *            The name of the package that the expressions are defined in.
     * @param context
     */
    public Parser(String packageName)
    {
        library = new ExpressionLibrary(packageName);

        input = new ArrayList<Expression<T>>();
        index = 0;

        opStack = new Stack<Expression<T>>();
        outStack = new Stack<Expression<T>>();
    }

    public Expression<T> parseExpression(String infix)
    {
        clear();

        input = library.<T> tokenize("[" + infix + "]");

        while (getNextToken() != null)
            parseNextToken();

        return outStack.pop();
    }

    /**
     * Resets the internal state once an expression is evaluated.
     * 
     */
    private void clear()
    {
        input.clear();
        opStack.clear();
        outStack.clear();
        index = 0;
        operatorExpected = false;
    }

    /**
     * Sends an expression to the output stack.
     */
    public void output(Expression<T> expression)
    {
        outStack.push(expression);
        operatorExpected = true;
    }

    /**
     * Pops a list of n expressions from the output stack, intended as arguments
     * for an expression currently being built.
     */
    public List<Expression<T>> getArguments(int n)
    {
        if (outStack.size() < n)
            throw new ArgumentsException();

        List<Expression<T>> results = new ArrayList<Expression<T>>();

        for (int i = 0; i < n; i++)
            results.add(0, outStack.pop());

        return results;
    }

    public List<Expression<T>> getArgumentsInReverse(int n)
    {
        List<Expression<T>> results = getArguments(n);

        Collections.reverse(results);

        return results;
    }

    /**
     * @return The top expression on the operator stack, or null if there is no
     *         such operator.
     */
    public Expression<T> topOperator()
    {
        if (!hasOperators())
            return null;
        else
            return opStack.peek();
    }

    /**
     * Places a new expression on top of the output stack.
     */
    public void addTopOperator(Expression<T> op)
    {
        opStack.push(op);
        operatorExpected = false;
    }

    /**
     * Builds the top operator on the output stack.
     */
    public void buildTopOperator()
    {
        opStack.pop().buildTree(this);
    }

    /**
     * @return true if there are operators on the output stack.
     */
    public boolean hasOperators()
    {
        return !opStack.isEmpty();
    }

    /**
     * For simple expressions, returns true if we expect a binary operator. May
     * not make sense in the general case but returns the right value for e.g.
     * subtraction vs. negation.
     */
    public boolean operatorExpected()
    {
        return operatorExpected;
    }

    public Expression<T> getNextToken()
    {
        if (index >= input.size())
            return null;
        else
            return input.get(index);
    }

    public void parseNextToken()
    {
        if (index >= input.size())
            throw new ArgumentsException();

        Expression<T> nextToken = input.get(index);
        index++;
        nextToken.parse(this);
    }

    public void discardNextToken()
    {
        if (index >= input.size())
            throw new ArgumentsException();

        index++;
    }
}
